﻿/**
 * MOTOR 2 - RAPID FLASH DYNAMICS
 * Copyright (c) 2007-2008, Michael Baczynski
 * Based on the Box2D Engine by Erin Catto, http://www.box2d.org
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the polygonal nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without specific
 *   prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package de.polygonal.motor2.collision.shapes {
	import de.polygonal.motor2.collision.nbody.IBroadPhase;
	import de.polygonal.motor2.collision.nbody.ShapeProxy;
	import de.polygonal.motor2.collision.shapes.ShapeTypes;
	import de.polygonal.motor2.collision.shapes.data.ShapeData;
	import de.polygonal.motor2.dynamics.RigidBody;
	import de.polygonal.motor2.math.ConvexBSPNode;
	import de.polygonal.motor2.math.Tri2;
	import de.polygonal.motor2.math.V2;		

	public class ShapeSkeleton {
		public var body:RigidBody;
		public var next:ShapeSkeleton;

		public var proxyId:int;
		public var type:int;

		public var friction:Number;
		public var restitution:Number;

		/* for requesting WCS tranform */
		public var synced:Boolean = false;

		/**
		 * model-space position, x-axis
		 */
		public var mx:Number;

		/**
		 * model-space position, y-axis
		 */
		public var my:Number;

		/**
		 * world-space position, x-axis
		 */
		public var x:Number;

		/**
		 * world-space position, y-axis
		 */
		public var y:Number;

		
		public var r11:Number, r12:Number;
		public var r21:Number, r22:Number;

		/* radius, radius^2 */
		public var radius:Number;
		public var radiusSq:Number;

		/* AABB, e=extends (halfwidths) */
		public var xmin:Number, xmax:Number, ex:Number;
		public var ymin:Number, ymax:Number, ey:Number;

		/* area and center of mass */
		public var area:Number;

		/* convex polygon */
		public var modelVertexChain:V2, modelNormalChain:V2;
		public var worldVertexChain:V2, worldNormalChain:V2;

		public var BSPNode:ConvexBSPNode;

		public var regularShape:Boolean;
		public var vertexCount:int;

		/**
		 * @private
		 */
		public var edges:Array, offsets:Array, edgeExt:Array, faceExt:Array;

		public var triangleList:Tri2;

		protected var _broadPhase:IBroadPhase;

		public function ShapeSkeleton(sd:ShapeData, rb:RigidBody) {
			friction = sd.friction;
			restitution = sd.restitution;
			body = rb;
			area = sd.area;
			
			setType();
			
			_broadPhase = body.world.getBroadPhase();
			proxyId = ShapeProxy.NULL_PROXY;
		}

		public function deconstruct():void {
			BSPNode = null;
			modelVertexChain = modelNormalChain = worldVertexChain = worldNormalChain = null;
			edges = offsets = edgeExt = faceExt = null;
			triangleList = null;
		}

		protected function createProxy(groupIndex:int, categoryBits:int, maskBits:int):void {
			update();
			
			if (_broadPhase.insideBounds(xmin, ymin, xmax, ymax)) {
				proxyId = _broadPhase.createProxy(groupIndex, categoryBits, maskBits, this);
			}
			else {
				proxyId = ShapeProxy.NULL_PROXY;
				body.freeze();
			}
		}

		protected function initPoly(vertexList:Array, vertexCount:int, regular:Boolean = true):void {
					
			var i:int;
			var ex:Number, ey:Number, nx:Number, ny:Number, mag:Number;
			var v:V2, v0:V2, v1:V2, mv:V2, wv:V2, mn:V2, wn:V2;
			
			regularShape = regular;
			
			/* setup chains */
			modelVertexChain = mv = vertexList[0];		
			worldVertexChain = wv = new V2();			
			mv.I = 0;
			wv.I = 0;
			
			modelNormalChain = mn = new V2();
			worldNormalChain = wn = new V2();
			mn.I = 0;
			wn.I = 0;
			
			//set up previous and next links to effectively
			//form a doubly-linked *circular* vertex list.
			for (i = 1;i < vertexCount; i++) {
				v = vertexList[i];
				
				// vertex in model space
				v0 = mv;
				v1 = v;
				v1.I = i;
				v0.next = v1;
				v1.prev = v0; 
				mv = mv.next;
				
				// vertex in world space
				v0 = wv;
				v1 = new V2();
				v1.I = i;
				v0.next = v1;
				v1.prev = v0;
				wv = wv.next;
				
				// normal in model space
				v0 = mn;
				v1 = new V2();
				v1.I = i;
				v0.next = v1;
				v1.prev = v0;
				mn = mn.next;
				
				// normal in world space
				v0 = wn;
				v1 = new V2();
				v1.I = i;
				v0.next = v1;
				v1.prev = v0;
				wn = wn.next;
			}
			
			/* make circular */
			mv.last = true;
			mv.next = modelVertexChain;
			modelVertexChain.prev = mv;
			
			wv.last = true;
			wv.next = worldVertexChain;
			worldVertexChain.prev = wv;
			
			mn.last = true;
			mn.next = modelNormalChain;
			modelNormalChain.prev = mn;
			
			wn.last = true;
			wn.next = worldNormalChain;
			worldNormalChain.prev = wn;
			
			/* precompute shape properties */ 
			edges = [];
			faceExt = [];
			edgeExt = [];
			offsets = [];
			
			mn = modelNormalChain;
			v0 = modelVertexChain;
			v1 = v0.next;
			for (i = 0;i < vertexCount; i++) {
				//normalized edge
				ex = v1.x - v0.x;
				ey = v1.y - v0.y;
				mag = Math.sqrt(ex * ex + ey * ey);
				ex /= mag;
				ey /= mag;
				edges[i] = new V2(ex, ey);
				
				nx = ey;
				ny = -ex;
				
				//edge normal
				mn.x = nx;
				mn.y = ny;
				mn = mn.next;
				
				//minimum distance center to face
				var midx:Number = v0.x + (v1.x - v0.x) / 2;
				var midy:Number = v0.y + (v1.y - v0.y) / 2;
				var ext:Number = midx * nx + midy * ny;
				edgeExt[i] = mag / 2;
				faceExt[i] = ext;
				
				//offset for non-regular polygons (otherwise 0)
				//mid(v0, v1) + -N(N * mid(v0, v1))
				if (!regular) offsets[i] = new V2(midx + (-nx * ext), midy + (-ny * ext));
				
				v0 = v1;
				v1 = v0.next;
			}
		}

		/**
		 * Computes a minimum bounding rectangle using brute-force from the
		 * shape's modeling space vertex chain.
		 */
		protected function computeMinAreaRect(pos:V2, ext:V2):Number {
			var minArea:Number = Number.MAX_VALUE, area:Number;
			var x0_x:Number, y0_x:Number, x1_x:Number, y1_x:Number;
			var x0_y:Number, y0_y:Number, x1_y:Number, y1_y:Number;
			
			var s0:Number, t0:Number, s1:Number, t1:Number;
			
			var ux:Number, nx:Number, dx:Number;
			var uy:Number, ny:Number, dy:Number;
			
			var x_theta:Number, y_theta:Number;
			var w:Number, h:Number, d:Number, l:Number;
			
			var v0:V2, v1:V2, r0:V2, rj:V2;
			
			var i:int;
			
			v0 = modelVertexChain;
			v1 = modelVertexChain.next;
			r0 = modelVertexChain;
			
			//TODO use while loop
			for (i = 0;i < vertexCount; i++) {
				ux = v1.x - v0.x;
				uy = v1.y - v0.y;
				l = Math.sqrt(ux * ux + uy * uy);
				ux /= l;
				uy /= l;
				
				nx = -uy;
				ny = ux;
				
				s0 = s1 = r0.x * ux + r0.y * uy;
				t0 = t1 = r0.x * nx + r0.y * ny;
				
				rj = modelVertexChain.next;
				while (true) {
					d = rj.x * ux + rj.y * uy;
					if (d < s0)
						s0 = d;
					else
					if (d > s1)
						s1 = d;
					
					d = rj.x * nx + rj.y * ny;
					if (d < t0)
						t0 = d;
					else
					if (d > t1)
						t1 = d;
					
					if (rj.last) break;
					rj = rj.next;
				}
				
				area = (s1 - s0) * (t1 - t0);
				if (area < minArea) {
					minArea = area;
					
					x0_x = ux * s0;
					y0_x = uy * s0;
					
					x1_x = ux * s1;
					y1_x = uy * s1;
					
					x0_y = nx * t0;
					y0_y = ny * t0;
					
					x1_y = nx * t1;
					y1_y = ny * t1;
				}
				
				v0 = v1;
				v1 = v1.next;
			}
			
			dx = x1_x - x0_x;
			dy = y1_x - y0_x;
			w = Math.sqrt(dx * dx + dy * dy);
			x_theta = Math.atan2(dy, dx);
			
			dx = x1_y - x0_y;
			dy = y1_y - y0_y;
			h = Math.sqrt(dx * dx + dy * dy);
			y_theta = Math.atan2(dy, dx);
			
			var rotation:Number;
			
			if ((y_theta < 0 ? -y_theta : y_theta) < (x_theta < 0 ? -x_theta : x_theta)) {
				rotation = y_theta;
				ext.x = h / 2;
				ext.y = w / 2;
			}
			else {
				rotation = x_theta;
				ext.x = w / 2;
				ext.y = h / 2;
			}
			
			//min + (max - min) / 2
			pos.x = (x0_x + x0_y) + ((x1_x + x1_y) - (x0_x + x0_y)) / 2;
			pos.y = (y0_x + y0_y) + ((y1_x + y1_y) - (y0_x + y0_y)) / 2;
			
			return rotation;
		}

		/**
		 * override for implementation
		 */
		public function update():void {
		}

		public function toWorldSpace():void {
		}

		public function closestPoint(x:Number, y:Number, out:V2):void {
		}

		public function pointInside(x:Number, y:Number):Boolean { 
			return false; 
		};

		public function modelSpacePos(p:V2):void {
		};

		public function triangulate():void {
		};

		protected function setType():void { 
			type = ShapeTypes.UNKNOWN; 
		}
	}
}
